package org.moonlight.jvm.rtda.share.heap;

import com.google.gson.Gson;
import lombok.EqualsAndHashCode;
import org.moonlight.jvm.classfile.ClassFile;
import org.moonlight.jvm.classpath.Classpath;
import org.moonlight.jvm.rtda.share.Object;
import org.moonlight.jvm.rtda.share.heap.classmember.AccessFlags;
import org.moonlight.jvm.rtda.share.heap.classmember.Field;
import org.moonlight.jvm.rtda.share.methodarea.RtConstantPool;
import org.moonlight.jvm.rtda.share.methodarea.StringPool;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * 类加载器
 *
 * @author Moonlight
 * @createTime 2023/8/29 16:55
 **/
@EqualsAndHashCode
public class ClassLoader {

    /** 类路径 - 类加载器需要类路径来搜索和读取class文件 **/
    private final Classpath classpath;
    /** 记录已经加载的类数据，key是类的完全限定名 - 这个map可以简单的视作方法区的实现 **/
    private final Map<String, Class> classMap;
    /** 是否输出类加载信息 **/
    private final boolean verboseClass;

    public ClassLoader(Classpath classpath, boolean verboseClass) {
        this.classpath = classpath;
        this.classMap = new HashMap<>();
        this.verboseClass = verboseClass;

        this.loadBasicClasses();
        this.loadPrimitiveClasses();
    }

    /**
     * 加载 java/lang/Class 并为已加载的类设置 JClass 属性 及 JClass 属性的 Extra 属性
     *
     * @createTime 16:35 2023/9/20
     * @author moonlight
     **/
    private void loadBasicClasses() {
        Class classClazz = this.loadClass("java/lang/Class");
        for (Class clazz : this.classMap.values()) {
            if (clazz.getJClass() == null) {
                clazz.setJClass(classClazz.newObject());
                clazz.getJClass().setExtra(clazz);
            }
        }
    }

    /**
     * 加载 基本类型 和 void 对应的类对象。
     * void和基本类型也有对应的类对象，但只能通过字面值来访问。如: void.class int.class...
     * 而且和数组类一样，基本类型的类也是由Java虚拟机在运行期间生成的
     *
     * @createTime 16:38 2023/9/20
     * @author moonlight
     **/
    private void loadPrimitiveClasses() {
        for (String type : Class.primitiveClassDescMapping.keySet()) {
            this.loadPrimitiveClass(type);
        }
    }

    /**
     * 第一，void和基本类型的类名就是void、int、float等。
     * 第二，基本类型的类没有超类，也没有实现任何接口。
     * 第三，非基本类型的类对象是通过ldc指令加载到操作数栈中的.
     *      而基本类型的类对象，虽然在Java代码中看起来是通过字面量获取的，
     *      但是编译之后的指令并不是ldc，而是getstatic。
     *      每个基本类型都有一个包装类，包装类中有一个静态常量，叫作TYPE，其中存放的就是基本类型的类
     *      如: java.lang.Integer 中的 public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
     *      也就是说，基本类型的类是通过getstatic指令访问相应包装类的TYPE字段加载到操作数栈中的。
     *      而 Class.getPrimitiveClass()是个本地方法
     *
     * @param className 类名
     * @createTime 16:41 2023/9/20
     * @author moonlight
     **/
    private void loadPrimitiveClass(String className) {
        Class clazz = new Class(AccessFlags.ACC_PUBLIC, className, this, true, null, null);
        clazz.setJClass(this.classMap.get("java/lang/Class").newObject());
        clazz.getJClass().setExtra(clazz);
        this.classMap.put(className, clazz);
    }

    /**
     * 根据传入的 className 加载指定的 class
     *  1. 先查找classMap，看类是否已经被加载
     *  2. 如果是，直接返回类数据，否则调用 {@link this#loadNonArrayClass(String)}方法加载类
     *
     * @param className class名称 - 全限定名, 如: java/lang/Object
     * @return {@link Class}
     * @createTime 9:00 2023/8/30
     * @author moonlight
     **/
    public Class loadClass(String className) {
        Class clazz = this.classMap.get(className);
        if (clazz != null) {
            // 类已经被加载了
//            System.out.println("类[" + className + "]已经被加载了");
            return clazz;
        }
        try {
           if (className.charAt(0) == '[') {
               clazz = loadArrayClass(className);
           } else {
               clazz = loadNonArrayClass(className);
           }
           Class classClazz;
           if ( (classClazz = this.classMap.get("java/lang/Class")) != null) {
               clazz.setJClass(classClazz.newObject());
               clazz.getJClass().setExtra(clazz);
           }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return clazz;
    }

    /**
     * 加载数组类. 数组在Java虚拟机中是个比较特殊的概念。数组类和普通的类是不同的。
     *   1. 普通的类从class文件中加载，但是数组类由Java虚拟机在运行时生成。
     *   2. 数组的类名是左方括号（[）+数组元素的类型描述符；数组的类型描述符就是类名本身。
     *      例如，int[]的类名是[I, int[][]的类名是[[I,Object[]的类名是[Ljava/lang/Object;
     *   3. 创建数组的方式和创建普通对象的方式不同。普通对象由new指令创建，然后由构造函数初始化。
     *      基本类型数组由 newarray指 令创建；引用类型数组由 anewarray指 令创建；另外还有一个专门的 multianewarray 指令用于创建多维数组。
     *   4. 普通对象中存放的是实例变量，通过putfield和getfield指令存取。
     *      数组对象中存放的则是数组元素，通过<t>aload和<t>astore系列指令按索引存取。
     *      其中<t>可以是a、b、c、d、f、i、l或者s，分别用于存取引用、byte、char、double、float、int、long或short类型的数组。
     *      另外，还有一个arraylength指令，用于获取数组长度。
     * @param className class名称 - 全限定名, 如: java/lang/Object
     * @return Class类对象(这里是数组类对象) {@link Class}
     * @createTime 10:47 2023/9/13
     * @author moonlight
     **/
    private Class loadArrayClass(String className) {
        Class clazz = new Class(
                AccessFlags.ACC_PUBLIC,
                className,
                // 类加载器
                this,
                // 数组不需要初始化，所以直接设置为true
                true,
                // 数组的父类直接是Object
                this.loadClass("java/lang/Object"),
                // 数组需要实现的接口
                new Class[]{ this.loadClass("java/lang/Cloneable"), this.loadClass("java/io/Serializable") });
        this.classMap.put(className, clazz);
        return clazz;
    }

    /**
     * 根据传入的 className 加载指定的 class
     * 该方法只加载非数组类型的类，数组类和普通类有很大的不同，它的数据并不是来自class文件，而是由Java虚拟机在运行期间生成。
     *  1. 先通过 {@link this#classpath} 查找并读取class文件
     *  2. 解析class文件{@link this#defineClass(byte[])}，生成虚拟机可以使用的类数据{@link Class}，并放入方法区{@link this#classMap}
     *  3. 对生成的类数据进行链接{@link this#linking(Class)}
     *
     * 标准的类加载过程有三步:
     *  1. Loading - ClassLoader将class文件加载到内存
     *  2. Linking - 对类数据进行链接，这个过程又分为三步:
     *     2.1. Verification(校验) -  {@link this#verification(Class)}
     *          验证类是否合法，保证加载的类能够被 JVM运行
     *          校验文件是否符合JVM规定
     *          校验版本兼容性
     *          校验引入的常量、引入的其他类是否存在
     *     2.2. Preparation(准备) -  {@link this#preparation(Class)}
     *          静态成员变量赋初始值
     *          final变量直接初始化为变量值
     *     2.3. Resolution(解析) -   {@link this#resolution(Class)}
     *          将类、方法、属性、对象引用等符号变量解析成直接变量， 其实就是把常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
     *  3. Initializing (初始化, 即 clinit 方法)
     *     按照从父类到子类，从上到下的顺序，收集 静态成员，静态代码块，组织成 clinit 方法, 并执行它，
     *     给静态成员赋初始值，执行静态代码块
     *  在本工程中，只实现了 1. Loading  2. Linking
     * @param className class名称 - 全限定名, 如: java/lang/Object
     * @return {@link Class}
     * @createTime 9:01 2023/8/30
     * @author moonlight
     **/
    private Class loadNonArrayClass(String className) throws Exception {
        byte[] data = this.classpath.readClass(className);
        if (data == null) {
            throw new ClassNotFoundException(className);
        }
        Class clazz = defineClass(data);
        linking(clazz);
        if (this.verboseClass) {
            System.out.println("[Loaded " + className + "]");
        }
        return clazz;
    }

    /**
     * 根据 从 class 文件中读取出来的字节数据 构建一个 {@link Class} 类对象，并将这个类对象存入到方法区中{@link this#classMap}
     *  1. 首先调用 {@link this#parseClass(byte[])} 函数把 class文件数据 转换成 {@link Class} 对象
     *  2. 设置这个 {@link Class} 对象 的 ClassLoader {@link Class#setLoader(ClassLoader)}
     *  3. 通过 {@link this#resolveSuperClass(Class)} 函数 解析它的父类 {@link Class#setSuperClass(Class)}
     *  4. 通过 {@link this#resolveInterfaces(Class)} 函数 解析它所实现的接口 {@link Class#setInterfaces(Class[])}
     *
     * @param classFileData byte数组，从 class 文件中读取出来的字节数据
     * @return {@link Class}
     * @createTime 9:09 2023/8/30
     * @author moonlight
     **/
    private Class defineClass(byte[] classFileData) {
        Class clazz = parseClass(classFileData);
        clazz.setLoader(this);
        resolveSuperClass(clazz);
        resolveInterfaces(clazz);
        this.classMap.put(clazz.getName(), clazz);
        return clazz;
    }

    /**
     * 解析 从 class 文件中读取出来的字节数据 构建出一个 {@link Class} 类对象，
     *  1. 通过 从 class 文件中读取出来的字节数据 解析出 {@link ClassFile} 对象
     *  2. 通过 解析出来的 {@link ClassFile} 对象 创建出 {@link Class} 对象
     * 具体的解析逻辑，都在 {@link ClassFile} 和 {@link Class} 的构造方法中
     *
     * @param classFileData byte数组，从 class 文件中读取出来的字节数据
     * @return {@link Class}
     * @createTime 9:18 2023/8/30
     * @author moonlight
     **/
    private Class parseClass(byte[] classFileData) {
        return new Class(new ClassFile(classFileData));
    }

    /**
     * 解析父类
     *
     * @param clazz {@link Class} 类对象
     * @createTime 9:27 2023/8/30
     * @author moonlight
     **/
    private void resolveSuperClass(Class clazz) {
        if (!"java/lang/Object".equals(clazz.getName())) {
            clazz.setSuperClass(clazz.getLoader().loadClass(clazz.getSuperClassName()));
        }
    }

    /**
     * 解析所实现的接口
     *
     * @param clazz {@link Class} 类对象
     * @createTime 9:27 2023/8/30
     * @author moonlight
     **/
    private void resolveInterfaces(Class clazz) {
        int length = clazz.getInterfaceNames().length;
        if (length > 0) {
            Class[] inters = new Class[length];
            ClassLoader loader = clazz.getLoader();
            int i = 0;
            for (String name : clazz.getInterfaceNames()) {
                inters[i++] = loader.loadClass(name);
            }
            clazz.setInterfaces(inters);
        }
    }

    /**
     * 类加载主要有三个过程：loading 、linking 、initializing；其中 linking又分为三个步骤: verification 、preparation 、resolution；
     *  1. verification(校验): 验证类是否合法，保证加载的类能够被 JVM运行
     *                        校验文件是否符合JVM规定
     *                        校验版本兼容性
     *                        校验引入的常量、引入的其他类是否存在
     *  2. preparation(准备): 静态成员变量赋初始值
     *                       final变量直接初始化为变量值
     *
     *  3. resolution(解析): 将类、方法、属性、对象引用等符号变量解析成直接变量
     *                      其实就是把常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
     *
     * @param clazz {@link Class} 类对象
     * @createTime 9:51 2023/8/30
     * @author moonlight
     **/
    private void linking(Class clazz) {
        verification(clazz);
        preparation(clazz);
        resolution(clazz);
    }

    /**
     * verification(校验): 验证类是否合法，保证加载的类能够被 JVM运行
     *                    校验文件是否符合JVM规定
     *                    校验版本兼容性
     *                    校验引入的常量、引入的其他类是否存在
     *
     * @param clazz {@link Class} 类对象
     * @createTime 9:58 2023/8/30
     * @author moonlight
     **/
    private void verification(Class clazz) {
        // TODO
    }

    /**
     * preparation(准备): 静态成员变量赋类型初始值
     *                   final常量直接初始化为变量值
     *
     * @param clazz {@link Class} 类对象
     * @createTime 9:58 2023/8/30
     * @author moonlight
     **/
    private void preparation(Class clazz) {
        calcInstanceFieldSlotIds(clazz);
        calcStaticFieldSlotIds(clazz);
        allocAndInitStaticVars(clazz);
    }

    /**
     * 计算实例字段的个数，同时给它们编号
     *
     * @param clazz {@link Class} 类对象
     * @createTime 10:27 2023/8/30
     * @author moonlight
     **/
    private void calcInstanceFieldSlotIds(Class clazz) {
        int slotId = 0;
        if (clazz.getSuperClass() != null) {
            slotId = clazz.getSuperClass().getInstanceSlotCount();
        }
        for (Field field : clazz.getFields()) {
            if (!field.isStatic()) {
                field.setSlotId(slotId);
                slotId++;
                // Long 和 Double 都是 64位，需要占用两个插槽，但是我们都是直接使用 num 的，就暂时不考虑了
//                if (field.isLongOrDouble()) {
//                    slotId++;
//                }
            }
        }
        clazz.setInstanceSlotCount(slotId);
    }

    /**
     * 计算静态字段的个数，同时给它们编号
     *
     * @param clazz {@link Class} 类对象
     * @createTime 10:27 2023/8/30
     * @author moonlight
     **/
    private void calcStaticFieldSlotIds(Class clazz) {
        int slotId = 0;
        for (Field field : clazz.getFields()) {
            if (field.isStatic()) {
                field.setSlotId(slotId);
                slotId++;
                // Long 和 Double 都是 64位，需要占用两个插槽，但是我们都是直接使用 num 的，就暂时不考虑了
//                if (field.isLongOrDouble()) {
//                    slotId++;
//                }
            }
        }
        clazz.setStaticSlotCount(slotId);
    }

    /**
     * final常量直接初始化为变量值
     *
     * @param clazz {@link Class} 类对象
     * @createTime 10:27 2023/8/30
     * @author moonlight
     **/
    private void allocAndInitStaticVars(Class clazz) {
        clazz.setStaticVars(new Slots(clazz.getStaticSlotCount()));
        for (Field field : clazz.getFields()) {
            if (field.isStatic() && field.isFinal()) {
                initStaticFinalVar(clazz, field);
            }
        }
    }

    /**
     * final常量初始化为变量值
     *
     * @param clazz {@link Class} 类对象
     * @createTime 10:27 2023/8/30
     * @author moonlight
     **/
    private void initStaticFinalVar(Class clazz, Field field) {
        Slots staticVars = clazz.getStaticVars();
        RtConstantPool rtCp = clazz.getRtCp();
        int cpIdx = field.getConstValueIndex();
        int slotId = field.getSlotId();

        if (cpIdx > 0) {
            switch (field.getDescriptor()) {
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    staticVars.setInt(slotId, (Integer) rtCp.getConstant(cpIdx));
                    break;
                case "F":
                    staticVars.setFloat(slotId, (Float) rtCp.getConstant(cpIdx));
                    break;
                case "J":
                    staticVars.setLong(slotId, (Long) rtCp.getConstant(cpIdx));
                    break;
                case "D":
                    staticVars.setDouble(slotId, (Double) rtCp.getConstant(cpIdx));
                    break;
                case "Ljava/lang/String;":
                    String constant = (String) rtCp.getConstant(cpIdx);
                    Object myString = StringPool.myString(this, constant);
                    staticVars.setRef(slotId, myString);
                    break;
                default:
                    throw new RuntimeException("不支持的常量字段描述符: " + field.getDescriptor() + ". 字段信息: " + new Gson().toJson(field));
            }
        }
    }

    /**
     * resolution(解析): 将类、方法、属性、对象引用等符号变量解析成直接变量
     *                  其实就是把常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
     *
     * @param clazz {@link Class} 类对象
     * @createTime 9:58 2023/8/30
     * @author moonlight
     **/
    private void resolution(Class clazz) {
        // TODO
    }

}
